#include "ImageFileCache.h"
#include <arkui/native_interface.h>
#include <arkui/native_node.h>
#include <cstdint>
#include <hilog/log.h>
#include <fstream>
#include <string>
#include <filesystem>
#include <sys/stat.h>
#include "ImageUtil.h"

const unsigned int LOG_PRINT_DOMAIN = 0xff00;
static ArkUI_NativeNodeAPI_1 *nodeAPI = reinterpret_cast<ArkUI_NativeNodeAPI_1 *>(
    OH_ArkUI_QueryModuleInterfaceByName(ARKUI_NATIVE_NODE, "ArkUI_NativeNodeAPI_1"));
napi_threadsafe_function ImageFileCache::g_cache_callback_handle = nullptr;
std::map<std::string, std::vector<ArkUI_NodeHandle>> ImageFileCache::imageHandlesWaitList;
ImageUtil ImageFileCache::util;
std::string ImageFileCache::cacheDir = "";
LRUCache ImageFileCache::lruCache;
ImageFileCache *ImageFileCache::instance = nullptr;
std::mutex ImageFileCache::sessionMutex;


static void sessionCallbackClose() {
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ImageFileCache", "Session Close");
}

ImageFileCache *ImageFileCache::GetInstance() {
    if (instance == nullptr) {
        instance = new ImageFileCache();
    }
    return instance;
}

void ImageFileCache::hm_threadsafe_func(napi_env env, napi_value js_fun, void *context, void *data) {
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ImageFileCache", "hm_threadsafe_func called");
    std::string *loadSrc = static_cast<std::string *>(data);
    UpdateNativeImage(*loadSrc);
    delete loadSrc;
}

void ImageFileCache::UpdateNativeImage(const std::string &loadSrc) {
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ImageFileCache", "UpdateNativeImage %{public}s",
                 loadSrc.c_str());

    std::string filePath = GetFullFilePatchInCache(loadSrc);
    for (auto imageNode : imageHandlesWaitList[loadSrc]) {
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ImageFileCache", "updateNativeImage %{pubic}p", imageNode);
        ArkUI_AttributeItem item0 = {.string = filePath.c_str()};
        nodeAPI->setAttribute(imageNode, NODE_IMAGE_SRC, &item0);
    }
    imageHandlesWaitList[loadSrc].clear();
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ImageFileCache", "UpdateNativeImage finish");
}

void ImageFileCache::ResponseCallbackFunc(const char *buffer, size_t bufferSize, const std::string &loadSrc) {
    FileSave(buffer, bufferSize, loadSrc);
    AddFileCacheMap(loadSrc, bufferSize);
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ImageFileCache", "ResponseCallBackFunc");

    void *data = new std::string(loadSrc);
    napi_status status = napi_call_threadsafe_function(g_cache_callback_handle, data, napi_tsfn_blocking);
}

void Rcp_ResponseCallback_func(void *usrCtx, Rcp_Response *response, uint32_t errCode) {
    Rcp_ResponseCallback_UserCtxt *context = static_cast<Rcp_ResponseCallback_UserCtxt *>(usrCtx);
    Rcp_Session *rcpSession;
    Rcp_Request *rcpRequest;
    {
        std::lock_guard<std::mutex> lock(ImageFileCache::sessionMutex);
        rcpSession = context->imageCache_userCtxt->sessionRequestMap[context->userCtxt_url].first;
        rcpRequest = context->imageCache_userCtxt->sessionRequestMap[context->userCtxt_url].second;
        context->imageCache_userCtxt->sessionRequestMap.erase(context->userCtxt_url);
    }
    if (errCode != 0 || response == NULL) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "ImageFileCache", "Rcp_Request fail, err: %{public}d",
                     errCode);
        delete context->responseCallbackObject;
        const Rcp_SessionConfiguration *sessionConfig = HMS_Rcp_GetSessionConfiguration(rcpSession);
        delete sessionConfig;
        return;
    }
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ImageFileCache", "Rcp_responseCallback body length %{public}d,",
                 response->body.length);

    if (!context) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "ImageFileCache", "context failure");
    }

    context->imageCache_userCtxt->ResponseCallbackFunc(response->body.buffer, response->body.length,
                                                       context->userCtxt_url);
    delete context->responseCallbackObject;
    const Rcp_SessionConfiguration *rcp_SessionConfiguration = HMS_Rcp_GetSessionConfiguration(rcpSession);
    delete rcp_SessionConfiguration;
    HMS_Rcp_DestroyRequest(rcpRequest);
    HMS_Rcp_CloseSession(&rcpSession);
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ImageFileCache", "Rcp_ResponseCallback_func finish");
}

void ImageFileCache::DownloadFetch(std::string src, size_t *bufferSize) {
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ImageFileCache", "DownloadFetch %{public}s", src.c_str());
    uint32_t errCode;

    Rcp_Headers *rcpRequestHeaders = HMS_Rcp_CreateHeaders();
    Rcp_RequestCookies *rcpCookies = HMS_Rcp_CreateRequestCookies();

    Rcp_SessionConfiguration *configuration = new Rcp_SessionConfiguration();
    memset(configuration, 0, sizeof(Rcp_SessionConfiguration));
    configuration->type = RCP_SESSION_TYPE_HTTP;
    configuration->sessionListener.onClosed = reinterpret_cast<void (*)(void)>(sessionCallbackClose);
    configuration->sessionListener.onCanceled = reinterpret_cast<void (*)(void)>(sessionCallbackClose);
    configuration->headers = rcpRequestHeaders;
    configuration->cookies = rcpCookies;

    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ImageFileCache", "CreateSessionBegin");

    Rcp_Session *rcpSession = HMS_Rcp_CreateSession(configuration, &errCode);

    Rcp_Request *rcpRequest = HMS_Rcp_CreateRequest(src.c_str());

    const char *sessionID = HMS_Rcp_GetSessionId(rcpSession);

    Rcp_ResponseCallbackObject *responseCallbackObject = new Rcp_ResponseCallbackObject();
    responseCallbackObject->callback = Rcp_ResponseCallback_func;
    Rcp_ResponseCallback_UserCtxt *rcp_ResponseCallback_UserCtxt =
        new Rcp_ResponseCallback_UserCtxt{this, src, responseCallbackObject};
    responseCallbackObject->usrCtx = rcp_ResponseCallback_UserCtxt;
    {
        std::lock_guard<std::mutex> lock(sessionMutex);
        sessionRequestMap[src] = std::make_pair(rcpSession, rcpRequest);
    }
    *bufferSize = 0;
    errCode = HMS_Rcp_Fetch(rcpSession, rcpRequest, responseCallbackObject);

    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ImageFileCache",
                 "DownloadFetch src %{public}s, session %{public}p, request %{public}p", src.c_str(), rcpSession,
                 rcpRequest);

    return;
}

void ImageFileCache::CancelRequest(std::string loadSrc) {
    Rcp_Session *rcpSession;
    Rcp_Request *rcpRequest;
    {
        std::lock_guard<std::mutex> lock(sessionMutex);
        if (sessionRequestMap.find(loadSrc) == sessionRequestMap.end()) {
            OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "ImageFileCache", "Cancel Request failed 1: %{public}s",
                         loadSrc.c_str());
            return;
        }
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ImageFileCache", "Cancel Request: %{public}s",
                     loadSrc.c_str());
        // if(imageHandlesWaitList[loadSrc].empty()) {
        // OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "ImageFileCache", "Cancel Request failed 2:
        // %{public}s", loadSrc.c_str()); return;
        // }
        rcpSession = sessionRequestMap[loadSrc].first;
        rcpRequest = sessionRequestMap[loadSrc].second;
        sessionRequestMap.erase(loadSrc);
    }

    auto errCode = HMS_Rcp_CancelRequest(rcpSession, rcpRequest);

    std::lock_guard<std::mutex> lock(onflightMutex);
    g_on_flight.erase(util.GenerateHash(loadSrc));
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ImageFileCache",
                 "rcpSession %{public}p, rcpRequest %{public}p, errCode %{public}d", rcpSession, rcpRequest, errCode);
}

const inline std::string ImageFileCache::GetFullFilePatchInCache(const std::string &loadSrc) {
    auto filekey = util.GenerateHash(loadSrc);
    return "file://" + GetSandBoxPath(filekey);
}

const inline std::string ImageFileCache::GetSandBoxPath(const std::string &fileKey) { return cacheDir + "/" + fileKey; }

void ImageFileCache::FileSave(const char *buffer, const size_t &bufferSize, const std::string &loadSrc) {
    if (cacheDir.length() < 1) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "ImageFileCache", "cache DIR empty");
        throw std::invalid_argument("CacheDir can't be empty");
        return;
    }
    auto fileKey = util.GenerateHash(loadSrc);
    auto sandboxPath = GetSandBoxPath(fileKey);
    std::ofstream outFile(sandboxPath, std::ios::binary);
    if (!outFile.is_open()) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "ImageFileCache", "Open file failed: %{public}s",
                     sandboxPath.c_str());
        return;
    }

    outFile.write(buffer, bufferSize);
    outFile.close();

    std::lock_guard<std::mutex> lock(onflightMutex);
    g_on_flight.erase(fileKey);
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ImageFileCache", "Write success: %{public}s, size %{public}lu",
                 sandboxPath.c_str(), bufferSize);
}

void ImageFileCache::InitCache(const std::string &cd, size_t cacheSize, napi_env env, napi_callback_info info) {
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ImageFileCache", "Init Cache dir:%{public}s, size %{public}lu",
                 cacheDir.c_str(), cacheSize);
    size_t argc = 1;
    napi_value args[1] = {NULL};
    napi_get_cb_info(env, info, &argc, args, NULL, NULL);

    napi_value safeFunc;
    napi_create_string_utf8(env, "threadsafe_func", sizeof("threadsafe_func"), &safeFunc);
    napi_status status = napi_create_threadsafe_function(env, args[0], NULL, safeFunc, 0, 1, NULL, NULL, NULL,
                                                         hm_threadsafe_func, &g_cache_callback_handle);

    SetImageFileCacheDir(cd);
    lruCache.init(cacheSize, cacheDir);
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ImageFileCache", " init end");
}

void ImageFileCache::AddFileCacheMap(const std::string loadSrc, size_t bufferSize) {
    auto fileKey = util.GenerateHash(loadSrc);
    lruCache.put(fileKey, bufferSize);
}

void ImageFileCache::SetImageFileCacheDir(const std::string &cd) {
    this->cacheDir = cd + "/ImageCache";
    if (mkdir(this->cacheDir.c_str(), 0755) == 0) {
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ImageFileCache", "setImageFileCache success %{public}s",
                     cacheDir.c_str());
    } else {
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ImageFileCache",
                     "setImageFileCache failed, already created %{public}s", cacheDir.c_str());
    }
}

void ImageFileCache::Preload(const std::string &loadSrc, const std::string &signature) {
    auto fileKey = util.GenerateHash(loadSrc);
    {
        std::lock_guard<std::mutex> lock(onflightMutex);
        g_on_flight[fileKey] = true;
    }

    size_t bufferSize;
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ImageFileCache", "FileKey: %{public}s", fileKey.c_str());
    if (lruCache.get(fileKey, bufferSize)) {
        return;
    }
    DownloadFetch(loadSrc, &bufferSize);
    return;
}

std::string ImageFileCache::GetImageCache(const std::string &loadSrc, ArkUI_NodeHandle imageHandle) {
    OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ImageFileCache", "GetImageCache %{public}s, %{public}p",
                 loadSrc.c_str(), imageHandle);
    auto fileKey = util.GenerateHash(loadSrc);
    size_t bufferSize;
    if (lruCache.get(fileKey, bufferSize)) {
        auto filePath = GetFullFilePatchInCache(loadSrc);
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "ImageFileCache", "GetImageCache path %{public}s",
                     filePath.c_str());
        return filePath;
    }

    bool indicator;
    {
        std::lock_guard<std::mutex> lock(onflightMutex);
        indicator = g_on_flight.find(fileKey) == g_on_flight.end();
    }
    if (indicator) {
        Preload(loadSrc);
    }
    imageHandlesWaitList[loadSrc].push_back(imageHandle);
    return "";
}
